/*
 *	Wireless Tools
 *
 *		Jean II - HPLB 97->99 - HPL 99->07
 *
 * Common subroutines to all the wireless tools...
 *
 * This file is released under the GPL license.
 *     Copyright (c) 1997-2007 Jean Tourrilhes <jt@hpl.hp.com>
 */

/***************************** INCLUDES *****************************/

#include "iwlib.h"		/* Header */

/************************ CONSTANTS & MACROS ************************/

/*
 * Constants fof WE-9->15
 */
#define IW15_MAX_FREQUENCIES	16
#define IW15_MAX_BITRATES	8
#define IW15_MAX_TXPOWER	8
#define IW15_MAX_ENCODING_SIZES	8
#define IW15_MAX_SPY		8
#define IW15_MAX_AP		8

/****************************** TYPES ******************************/

/*
 *	Struct iw_range up to WE-15
 */
struct	iw15_range
{
    __u32		throughput;
    __u32		min_nwid;
    __u32		max_nwid;
    __u16		num_channels;
    __u8		num_frequency;
    struct iw_freq	freq[IW15_MAX_FREQUENCIES];
    __s32		sensitivity;
    struct iw_quality	max_qual;
    __u8		num_bitrates;
    __s32		bitrate[IW15_MAX_BITRATES];
    __s32		min_rts;
    __s32		max_rts;
    __s32		min_frag;
    __s32		max_frag;
    __s32		min_pmp;
    __s32		max_pmp;
    __s32		min_pmt;
    __s32		max_pmt;
    __u16		pmp_flags;
    __u16		pmt_flags;
    __u16		pm_capa;
    __u16		encoding_size[IW15_MAX_ENCODING_SIZES];
    __u8		num_encoding_sizes;
    __u8		max_encoding_tokens;
    __u16		txpower_capa;
    __u8		num_txpower;
    __s32		txpower[IW15_MAX_TXPOWER];
    __u8		we_version_compiled;
    __u8		we_version_source;
    __u16		retry_capa;
    __u16		retry_flags;
    __u16		r_time_flags;
    __s32		min_retry;
    __s32		max_retry;
    __s32		min_r_time;
    __s32		max_r_time;
    struct iw_quality	avg_qual;
};

/*
 * Union for all the versions of iwrange.
 * Fortunately, I mostly only add fields at the end, and big-bang
 * reorganisations are few.
 */
union	iw_range_raw
{
    struct iw15_range	range15;	/* WE 9->15 */
    struct iw_range		range;		/* WE 16->current */
};

/*
 * Offsets in iw_range struct
 */
#define iwr15_off(f)	( ((char *) &(((struct iw15_range *) NULL)->f)) - \
        (char *) NULL)
#define iwr_off(f)	( ((char *) &(((struct iw_range *) NULL)->f)) - \
        (char *) NULL)

/**************************** VARIABLES ****************************/

/* Modes as human readable strings */
const char * const iw_operation_mode[] = { "Auto",
    "Ad-Hoc",
    "Managed",
    "Master",
    "Repeater",
    "Secondary",
    "Monitor",
    "Unknown/bug" };

/* Modulations as human readable strings */
const struct iw_modul_descr	iw_modul_list[] = {
    /* Start with aggregate types, so that they display first */
    { IW_MODUL_11AG, "11ag",
        "IEEE 802.11a + 802.11g (2.4 & 5 GHz, up to 54 Mb/s)" },
    { IW_MODUL_11AB, "11ab",
        "IEEE 802.11a + 802.11b (2.4 & 5 GHz, up to 54 Mb/s)" },
    { IW_MODUL_11G, "11g", "IEEE 802.11g (2.4 GHz, up to 54 Mb/s)" },
    { IW_MODUL_11A, "11a", "IEEE 802.11a (5 GHz, up to 54 Mb/s)" },
    { IW_MODUL_11B, "11b", "IEEE 802.11b (2.4 GHz, up to 11 Mb/s)" },

    /* Proprietary aggregates */
    { IW_MODUL_TURBO | IW_MODUL_11A, "turboa",
        "Atheros turbo mode at 5 GHz (up to 108 Mb/s)" },
    { IW_MODUL_TURBO | IW_MODUL_11G, "turbog",
        "Atheros turbo mode at 2.4 GHz (up to 108 Mb/s)" },
    { IW_MODUL_PBCC | IW_MODUL_11B, "11+",
        "TI 802.11+ (2.4 GHz, up to 22 Mb/s)" },

    /* Individual modulations */
    { IW_MODUL_OFDM_G, "OFDMg",
        "802.11g higher rates, OFDM at 2.4 GHz (up to 54 Mb/s)" },
    { IW_MODUL_OFDM_A, "OFDMa", "802.11a, OFDM at 5 GHz (up to 54 Mb/s)" },
    { IW_MODUL_CCK, "CCK", "802.11b higher rates (2.4 GHz, up to 11 Mb/s)" },
    { IW_MODUL_DS, "DS", "802.11 Direct Sequence (2.4 GHz, up to 2 Mb/s)" },
    { IW_MODUL_FH, "FH", "802.11 Frequency Hopping (2,4 GHz, up to 2 Mb/s)" },

    /* Proprietary modulations */
    { IW_MODUL_TURBO, "turbo",
        "Atheros turbo mode, channel bonding (up to 108 Mb/s)" },
    { IW_MODUL_PBCC, "PBCC",
        "TI 802.11+ higher rates (2.4 GHz, up to 22 Mb/s)" },
    { IW_MODUL_CUSTOM, "custom",
        "Driver specific modulation (check driver documentation)" },
};

/* Disable runtime version warning in iw_get_range_info() */
int	iw_ignore_version = 0;

/************************ SOCKET SUBROUTINES *************************/

/*------------------------------------------------------------------*/
/*
 * Open a socket.
 * Depending on the protocol present, open the right socket. The socket
 * will allow us to talk to the driver.
 */
    int
iw_sockets_open(void)
{
    static const int families[] = {
        AF_INET, AF_IPX, AF_AX25, AF_APPLETALK
    };
    unsigned int	i;
    int		sock;

    /*
     * Now pick any (exisiting) useful socket family for generic queries
     * Note : don't open all the socket, only returns when one matches,
     * all protocols might not be valid.
     * Workaround by Jim Kaba <jkaba@sarnoff.com>
     * Note : in 99% of the case, we will just open the inet_sock.
     * The remaining 1% case are not fully correct...
     */

    /* Try all families we support */
    for(i = 0; i < sizeof(families)/sizeof(int); ++i)
    {
        /* Try to open the socket, if success returns it */
        sock = socket(families[i], SOCK_DGRAM, 0);
        if(sock >= 0)
            return sock;
    }

    return -1;
}

/*------------------------------------------------------------------*/
/*
 * Extract the interface name out of /proc/net/wireless or /proc/net/dev.
 */
    static inline char *
iw_get_ifname(char *	name,	/* Where to store the name */
        int	nsize,	/* Size of name buffer */
        char *	buf)	/* Current position in buffer */
{
    char *	end;

    /* Skip leading spaces */
    while(isspace(*buf))
        buf++;

#ifndef IW_RESTRIC_ENUM
    /* Get name up to the last ':'. Aliases may contain ':' in them,
     * but the last one should be the separator */
    end = strrchr(buf, ':');
#else
    /* Get name up to ": "
     * Note : we compare to ": " to make sure to process aliased interfaces
     * properly. Doesn't work on /proc/net/dev, because it doesn't guarantee
     * a ' ' after the ':'*/
    end = strstr(buf, ": ");
#endif

    /* Not found ??? To big ??? */
    if((end == NULL) || (((end - buf) + 1) > nsize))
        return(NULL);

    /* Copy */
    memcpy(name, buf, (end - buf));
    name[end - buf] = '\0';

    /* Return value currently unused, just make sure it's non-NULL */
    return(end);
}

/*------------------------------------------------------------------*/
/*
 * Enumerate devices and call specified routine
 * The new way just use /proc/net/wireless, so get all wireless interfaces,
 * whether configured or not. This is the default if available.
 * The old way use SIOCGIFCONF, so get only configured interfaces (wireless
 * or not).
 */
    void
iw_enum_devices(int		skfd,
        iw_enum_handler	fn,
        char *		args[],
        int		count)
{
    char		buff[1024];
    FILE *	fh;
    struct ifconf ifc;
    struct ifreq *ifr;
    int		i;

#ifndef IW_RESTRIC_ENUM
    /* Check if /proc/net/dev is available */
    fh = fopen(PROC_NET_DEV, "r");
#else
    /* Check if /proc/net/wireless is available */
    fh = fopen(PROC_NET_WIRELESS, "r");
#endif

    if(fh != NULL)
    {
        /* Success : use data from /proc/net/wireless */

        /* Eat 2 lines of header */
        fgets(buff, sizeof(buff), fh);
        fgets(buff, sizeof(buff), fh);

        /* Read each device line */
        while(fgets(buff, sizeof(buff), fh))
        {
            char	name[IFNAMSIZ + 1];
            char *s;

            /* Skip empty or almost empty lines. It seems that in some
             * cases fgets return a line with only a newline. */
            if((buff[0] == '\0') || (buff[1] == '\0'))
                continue;

            /* Extract interface name */
            s = iw_get_ifname(name, sizeof(name), buff);

            if(!s)
            {
                /* Failed to parse, complain and continue */
#ifndef IW_RESTRIC_ENUM
                fprintf(stderr, "Cannot parse " PROC_NET_DEV "\n");
#else
                fprintf(stderr, "Cannot parse " PROC_NET_WIRELESS "\n");
#endif
            }
            else
                /* Got it, print info about this interface */
                (*fn)(skfd, name, args, count);
        }

        fclose(fh);
    }
    else
    {
        /* Get list of configured devices using "traditional" way */
        ifc.ifc_len = sizeof(buff);
        ifc.ifc_buf = buff;
        if(ioctl(skfd, SIOCGIFCONF, &ifc) < 0)
        {
            fprintf(stderr, "SIOCGIFCONF: %s\n", strerror(errno));
            return;
        }
        ifr = ifc.ifc_req;

        /* Print them */
        for(i = ifc.ifc_len / sizeof(struct ifreq); --i >= 0; ifr++)
            (*fn)(skfd, ifr->ifr_name, args, count);
    }
}

/*********************** WIRELESS SUBROUTINES ************************/

/*------------------------------------------------------------------*/
/*
 * Extract WE version number from /proc/net/wireless
 * In most cases, you really want to get version information from
 * the range info (range->we_version_compiled), see below...
 *
 * If we have WE-16 and later, the WE version is available at the
 * end of the header line of the file.
 * For version prior to that, we can only detect the change from
 * v11 to v12, so we do an approximate job. Fortunately, v12 to v15
 * are highly binary compatible (on the struct level).
 */
    int
iw_get_kernel_we_version(void)
{
    char		buff[1024];
    FILE *	fh;
    char *	p;
    int		v;

    /* Check if /proc/net/wireless is available */
    fh = fopen(PROC_NET_WIRELESS, "r");

    if(fh == NULL)
    {
        fprintf(stderr, "Cannot read " PROC_NET_WIRELESS "\n");
        return(-1);
    }

    /* Read the first line of buffer */
    fgets(buff, sizeof(buff), fh);

    if(strstr(buff, "| WE") == NULL)
    {
        /* Prior to WE16, so explicit version not present */

        /* Black magic */
        if(strstr(buff, "| Missed") == NULL)
            v = 11;
        else
            v = 15;
        fclose(fh);
        return(v);
    }

    /* Read the second line of buffer */
    fgets(buff, sizeof(buff), fh);

    /* Get to the last separator, to get the version */
    p = strrchr(buff, '|');
    if((p == NULL) || (sscanf(p + 1, "%d", &v) != 1))
    {
        fprintf(stderr, "Cannot parse " PROC_NET_WIRELESS "\n");
        fclose(fh);
        return(-1);
    }

    fclose(fh);
    return(v);
}

/*------------------------------------------------------------------*/
/*
 * Print the WE versions of the interface.
 */
    static int
print_iface_version_info(int	skfd,
        char *	ifname,
        char *	args[],		/* Command line args */
        int	count)		/* Args count */
{
    struct iwreq		wrq;
    char			buffer[sizeof(iwrange) * 2];	/* Large enough */
    struct iw_range *	range;

    /* Avoid "Unused parameter" warning */
    args = args; count = count;

    /* If no wireless name : no wireless extensions.
     * This enable us to treat the SIOCGIWRANGE failure below properly. */
    if(iw_get_ext(skfd, ifname, SIOCGIWNAME, &wrq) < 0)
        return(-1);

    /* Cleanup */
    memset(buffer, 0, sizeof(buffer));

    wrq.u.data.pointer = (caddr_t) buffer;
    wrq.u.data.length = sizeof(buffer);
    wrq.u.data.flags = 0;
    if(iw_get_ext(skfd, ifname, SIOCGIWRANGE, &wrq) < 0)
    {
        /* Interface support WE (see above), but not IWRANGE */
        fprintf(stderr, "%-8.16s  Driver has no Wireless Extension version information.\n\n", ifname);
        return(0);
    }

    /* Copy stuff at the right place, ignore extra */
    range = (struct iw_range *) buffer;

    /* For new versions, we can check the version directly, for old versions
     * we use magic. 300 bytes is a also magic number, don't touch... */
    if(wrq.u.data.length >= 300)
    {
        /* Version is always at the same offset, so it's ok */
        printf("%-8.16s  Recommend Wireless Extension v%d or later,\n",
                ifname, range->we_version_source);
        printf("          Currently compiled with Wireless Extension v%d.\n\n",
                range->we_version_compiled);
    }
    else
    {
        fprintf(stderr, "%-8.16s  Wireless Extension version too old.\n\n",
                ifname);
    }


    return(0);
}

/*------------------------------------------------------------------*/
/*
 * Print the WE versions of the tools.
 */
    int
iw_print_version_info(const char *	toolname)
{
    int		skfd;			/* generic raw socket desc.	*/
    int		we_kernel_version;

    /* Create a channel to the NET kernel. */
    if((skfd = iw_sockets_open()) < 0)
    {
        perror("socket");
        return -1;
    }

    /* Information about the tools themselves */
    if(toolname != NULL)
        printf("%-8.16s  Wireless-Tools version %d\n", toolname, WT_VERSION);
    printf("          Compatible with Wireless Extension v11 to v%d.\n\n",
            WE_MAX_VERSION);

    /* Get version from kernel */
    we_kernel_version = iw_get_kernel_we_version();
    /* Only version >= 16 can be verified, other are guessed */
    if(we_kernel_version > 15)
        printf("Kernel    Currently compiled with Wireless Extension v%d.\n\n",
                we_kernel_version);

    /* Version for each device */
    iw_enum_devices(skfd, &print_iface_version_info, NULL, 0);

    iw_sockets_close(skfd);

    return 0;
}

/*------------------------------------------------------------------*/
/*
 * Get the range information out of the driver
 */
    int
iw_get_range_info(int		skfd,
        const char *	ifname,
        iwrange *	range)
{
    struct iwreq		wrq;
    char			buffer[sizeof(iwrange) * 2];	/* Large enough */
    union iw_range_raw *	range_raw;

    /* Cleanup */
    bzero(buffer, sizeof(buffer));

    wrq.u.data.pointer = (caddr_t) buffer;
    wrq.u.data.length = sizeof(buffer);
    wrq.u.data.flags = 0;
    if(iw_get_ext(skfd, ifname, SIOCGIWRANGE, &wrq) < 0)
        return(-1);

    /* Point to the buffer */
    range_raw = (union iw_range_raw *) buffer;

    /* For new versions, we can check the version directly, for old versions
     * we use magic. 300 bytes is a also magic number, don't touch... */
    if(wrq.u.data.length < 300)
    {
        /* That's v10 or earlier. Ouch ! Let's make a guess...*/
        range_raw->range.we_version_compiled = 9;
    }

    /* Check how it needs to be processed */
    if(range_raw->range.we_version_compiled > 15)
    {
        /* This is our native format, that's easy... */
        /* Copy stuff at the right place, ignore extra */
        memcpy((char *) range, buffer, sizeof(iwrange));
    }
    else
    {
        /* Zero unknown fields */
        bzero((char *) range, sizeof(struct iw_range));

        /* Initial part unmoved */
        memcpy((char *) range,
                buffer,
                iwr15_off(num_channels));
        /* Frequencies pushed futher down towards the end */
        memcpy((char *) range + iwr_off(num_channels),
                buffer + iwr15_off(num_channels),
                iwr15_off(sensitivity) - iwr15_off(num_channels));
        /* This one moved up */
        memcpy((char *) range + iwr_off(sensitivity),
                buffer + iwr15_off(sensitivity),
                iwr15_off(num_bitrates) - iwr15_off(sensitivity));
        /* This one goes after avg_qual */
        memcpy((char *) range + iwr_off(num_bitrates),
                buffer + iwr15_off(num_bitrates),
                iwr15_off(min_rts) - iwr15_off(num_bitrates));
        /* Number of bitrates has changed, put it after */
        memcpy((char *) range + iwr_off(min_rts),
                buffer + iwr15_off(min_rts),
                iwr15_off(txpower_capa) - iwr15_off(min_rts));
        /* Added encoding_login_index, put it after */
        memcpy((char *) range + iwr_off(txpower_capa),
                buffer + iwr15_off(txpower_capa),
                iwr15_off(txpower) - iwr15_off(txpower_capa));
        /* Hum... That's an unexpected glitch. Bummer. */
        memcpy((char *) range + iwr_off(txpower),
                buffer + iwr15_off(txpower),
                iwr15_off(avg_qual) - iwr15_off(txpower));
        /* Avg qual moved up next to max_qual */
        memcpy((char *) range + iwr_off(avg_qual),
                buffer + iwr15_off(avg_qual),
                sizeof(struct iw_quality));
    }

    /* We are now checking much less than we used to do, because we can
     * accomodate more WE version. But, there are still cases where things
     * will break... */
    if(!iw_ignore_version)
    {
        /* We don't like very old version (unfortunately kernel 2.2.X) */
        if(range->we_version_compiled <= 10)
        {
            fprintf(stderr, "Warning: Driver for device %s has been compiled with an ancient version\n", ifname);
            fprintf(stderr, "of Wireless Extension, while this program support version 11 and later.\n");
            fprintf(stderr, "Some things may be broken...\n\n");
        }

        /* We don't like future versions of WE, because we can't cope with
         * the unknown */
        if(range->we_version_compiled > WE_MAX_VERSION)
        {
            fprintf(stderr, "Warning: Driver for device %s has been compiled with version %d\n", ifname, range->we_version_compiled);
            fprintf(stderr, "of Wireless Extension, while this program supports up to version %d.\n", WE_MAX_VERSION);
            fprintf(stderr, "Some things may be broken...\n\n");
        }

        /* Driver version verification */
        if((range->we_version_compiled > 10) &&
                (range->we_version_compiled < range->we_version_source))
        {
            fprintf(stderr, "Warning: Driver for device %s recommend version %d of Wireless Extension,\n", ifname, range->we_version_source);
            fprintf(stderr, "but has been compiled with version %d, therefore some driver features\n", range->we_version_compiled);
            fprintf(stderr, "may not be available...\n\n");
        }
        /* Note : we are only trying to catch compile difference, not source.
         * If the driver source has not been updated to the latest, it doesn't
         * matter because the new fields are set to zero */
    }

    /* Don't complain twice.
     * In theory, the test apply to each individual driver, but usually
     * all drivers are compiled from the same kernel. */
    iw_ignore_version = 1;

    return(0);
}

/*------------------------------------------------------------------*/
/*
 * Get information about what private ioctls are supported by the driver
 *
 * Note : there is one danger using this function. If it return 0, you
 * still need to free() the buffer. Beware.
 */
    int
iw_get_priv_info(int		skfd,
        const char *	ifname,
        iwprivargs **	ppriv)
{
    struct iwreq		wrq;
    iwprivargs *		priv = NULL;	/* Not allocated yet */
    int			maxpriv = 16;	/* Minimum for compatibility WE<13 */
    iwprivargs *		newpriv;

    /* Some driver may return a very large number of ioctls. Some
     * others a very small number. We now use a dynamic allocation
     * of the array to satisfy everybody. Of course, as we don't know
     * in advance the size of the array, we try various increasing
     * sizes. Jean II */
    do
    {
        /* (Re)allocate the buffer */
        newpriv = realloc(priv, maxpriv * sizeof(priv[0]));
        if(newpriv == NULL)
        {
            fprintf(stderr, "%s: Allocation failed\n", __FUNCTION__);
            break;
        }
        priv = newpriv;

        /* Ask the driver if it's large enough */
        wrq.u.data.pointer = (caddr_t) priv;
        wrq.u.data.length = maxpriv;
        wrq.u.data.flags = 0;
        if(iw_get_ext(skfd, ifname, SIOCGIWPRIV, &wrq) >= 0)
        {
            /* Success. Pass the buffer by pointer */
            *ppriv = priv;
            /* Return the number of ioctls */
            return(wrq.u.data.length);
        }

        /* Only E2BIG means the buffer was too small, abort on other errors */
        if(errno != E2BIG)
        {
            /* Most likely "not supported". Don't barf. */
            break;
        }

        /* Failed. We probably need a bigger buffer. Check if the kernel
         * gave us any hints. */
        if(wrq.u.data.length > maxpriv)
            maxpriv = wrq.u.data.length;
        else
            maxpriv *= 2;
    }
    while(maxpriv < 1000);

    /* Cleanup */
    if(priv)
        free(priv);
    *ppriv = NULL;

    return(-1);
}

/*------------------------------------------------------------------*/
/*
 * Get essential wireless config from the device driver
 * We will call all the classical wireless ioctl on the driver through
 * the socket to know what is supported and to get the settings...
 * Note : compare to the version in iwconfig, we extract only
 * what's *really* needed to configure a device...
 */
    int
iw_get_basic_config(int			skfd,
        const char *	ifname,
        wireless_config *	info)
{
    struct iwreq		wrq;

    memset((char *) info, 0, sizeof(struct wireless_config));

    /* Get wireless name */
    if(iw_get_ext(skfd, ifname, SIOCGIWNAME, &wrq) < 0)
        /* If no wireless name : no wireless extensions */
        return(-1);
    else
    {
        strncpy(info->name, wrq.u.name, IFNAMSIZ);
        info->name[IFNAMSIZ] = '\0';
    }

    /* Get network ID */
    if(iw_get_ext(skfd, ifname, SIOCGIWNWID, &wrq) >= 0)
    {
        info->has_nwid = 1;
        memcpy(&(info->nwid), &(wrq.u.nwid), sizeof(iwparam));
    }

    /* Get frequency / channel */
    if(iw_get_ext(skfd, ifname, SIOCGIWFREQ, &wrq) >= 0)
    {
        info->has_freq = 1;
        info->freq = iw_freq2float(&(wrq.u.freq));
        info->freq_flags = wrq.u.freq.flags;
    }

    /* Get encryption information */
    wrq.u.data.pointer = (caddr_t) info->key;
    wrq.u.data.length = IW_ENCODING_TOKEN_MAX;
    wrq.u.data.flags = 0;
    if(iw_get_ext(skfd, ifname, SIOCGIWENCODE, &wrq) >= 0)
    {
        info->has_key = 1;
        info->key_size = wrq.u.data.length;
        info->key_flags = wrq.u.data.flags;
    }

    /* Get ESSID */
    wrq.u.essid.pointer = (caddr_t) info->essid;
    wrq.u.essid.length = IW_ESSID_MAX_SIZE + 1;
    wrq.u.essid.flags = 0;
    if(iw_get_ext(skfd, ifname, SIOCGIWESSID, &wrq) >= 0)
    {
        info->has_essid = 1;
        info->essid_on = wrq.u.data.flags;
    }

    /* Get operation mode */
    if(iw_get_ext(skfd, ifname, SIOCGIWMODE, &wrq) >= 0)
    {
        info->has_mode = 1;
        /* Note : event->u.mode is unsigned, no need to check <= 0 */
        if(wrq.u.mode < IW_NUM_OPER_MODE)
            info->mode = wrq.u.mode;
        else
            info->mode = IW_NUM_OPER_MODE;	/* Unknown/bug */
    }

    return(0);
}

/*------------------------------------------------------------------*/
/*
 * Set essential wireless config in the device driver
 * We will call all the classical wireless ioctl on the driver through
 * the socket to know what is supported and to set the settings...
 * We support only the restricted set as above...
 */
    int
iw_set_basic_config(int			skfd,
        const char *	ifname,
        wireless_config *	info)
{
    struct iwreq		wrq;
    int			ret = 0;

    /* Get wireless name (check if interface is valid) */
    if(iw_get_ext(skfd, ifname, SIOCGIWNAME, &wrq) < 0)
        /* If no wireless name : no wireless extensions */
        return(-2);

    /* Set the current mode of operation
     * Mode need to be first : some settings apply only in a specific mode
     * (such as frequency).
     */
    if(info->has_mode)
    {
        strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
        wrq.u.mode = info->mode;

        if(iw_get_ext(skfd, ifname, SIOCSIWMODE, &wrq) < 0)
        {
            fprintf(stderr, "SIOCSIWMODE: %s\n", strerror(errno));
            ret = -1;
        }
    }

    /* Set frequency / channel */
    if(info->has_freq)
    {
        iw_float2freq(info->freq, &(wrq.u.freq));

        if(iw_set_ext(skfd, ifname, SIOCSIWFREQ, &wrq) < 0)
        {
            fprintf(stderr, "SIOCSIWFREQ: %s\n", strerror(errno));
            ret = -1;
        }
    }

    /* Set encryption information */
    if(info->has_key)
    {
        int		flags = info->key_flags;

        /* Check if there is a key index */
        if((flags & IW_ENCODE_INDEX) > 0)
        {
            /* Set the index */
            wrq.u.data.pointer = (caddr_t) NULL;
            wrq.u.data.flags = (flags & (IW_ENCODE_INDEX)) | IW_ENCODE_NOKEY;
            wrq.u.data.length = 0;

            if(iw_set_ext(skfd, ifname, SIOCSIWENCODE, &wrq) < 0)
            {
                fprintf(stderr, "SIOCSIWENCODE(%d): %s\n",
                        errno, strerror(errno));
                ret = -1;
            }
        }

        /* Mask out index to minimise probability of reject when setting key */
        flags = flags & (~IW_ENCODE_INDEX);

        /* Set the key itself (set current key in this case) */
        wrq.u.data.pointer = (caddr_t) info->key;
        wrq.u.data.length = info->key_size;
        wrq.u.data.flags = flags;

        /* Compatibility with WE<13 */
        if(flags & IW_ENCODE_NOKEY)
            wrq.u.data.pointer = NULL;

        if(iw_set_ext(skfd, ifname, SIOCSIWENCODE, &wrq) < 0)
        {
            fprintf(stderr, "SIOCSIWENCODE(%d): %s\n",
                    errno, strerror(errno));
            ret = -1;
        }
    }

    /* Set Network ID, if available (this is for non-802.11 cards) */
    if(info->has_nwid)
    {
        memcpy(&(wrq.u.nwid), &(info->nwid), sizeof(iwparam));
        wrq.u.nwid.fixed = 1;	/* Hum... When in Rome... */

        if(iw_set_ext(skfd, ifname, SIOCSIWNWID, &wrq) < 0)
        {
            fprintf(stderr, "SIOCSIWNWID: %s\n", strerror(errno));
            ret = -1;
        }
    }

    /* Set ESSID (extended network), if available.
     * ESSID need to be last : most device re-perform the scanning/discovery
     * when this is set, and things like encryption keys are better be
     * defined if we want to discover the right set of APs/nodes.
     */
    if(info->has_essid)
    {
        int		we_kernel_version;
        we_kernel_version = iw_get_kernel_we_version();

        wrq.u.essid.pointer = (caddr_t) info->essid;
        wrq.u.essid.length = strlen(info->essid);
        wrq.u.data.flags = info->essid_on;
        if(we_kernel_version < 21)
            wrq.u.essid.length++;

        if(iw_set_ext(skfd, ifname, SIOCSIWESSID, &wrq) < 0)
        {
            fprintf(stderr, "SIOCSIWESSID: %s\n", strerror(errno));
            ret = -1;
        }
    }

    return(ret);
}

/*********************** PROTOCOL SUBROUTINES ***********************/
/*
 * Fun stuff with protocol identifiers (SIOCGIWNAME).
 * We assume that drivers are returning sensible values in there,
 * which is not always the case :-(
 */

/*------------------------------------------------------------------*/
/*
 * Compare protocol identifiers.
 * We don't want to know if the two protocols are the exactly same,
 * but if they interoperate at some level, and also if they accept the
 * same type of config (ESSID vs NWID, freq...).
 * This is supposed to work around the alphabet soup.
 * Return 1 if protocols are compatible, 0 otherwise
 */
    int
iw_protocol_compare(const char *	protocol1,
        const char *	protocol2)
{
    const char *	dot11 = "IEEE 802.11";
    const char *	dot11_ds = "Dbg";
    const char *	dot11_5g = "a";

    /* If the strings are the same -> easy */
    if(!strncmp(protocol1, protocol2, IFNAMSIZ))
        return(1);

    /* Are we dealing with one of the 802.11 variant ? */
    if( (!strncmp(protocol1, dot11, strlen(dot11))) &&
            (!strncmp(protocol2, dot11, strlen(dot11))) )
    {
        const char *	sub1 = protocol1 + strlen(dot11);
        const char *	sub2 = protocol2 + strlen(dot11);
        unsigned int	i;
        int		isds1 = 0;
        int		isds2 = 0;
        int		is5g1 = 0;
        int		is5g2 = 0;

        /* Check if we find the magic letters telling it's DS compatible */
        for(i = 0; i < strlen(dot11_ds); i++)
        {
            if(strchr(sub1, dot11_ds[i]) != NULL)
                isds1 = 1;
            if(strchr(sub2, dot11_ds[i]) != NULL)
                isds2 = 1;
        }
        if(isds1 && isds2)
            return(1);

        /* Check if we find the magic letters telling it's 5GHz compatible */
        for(i = 0; i < strlen(dot11_5g); i++)
        {
            if(strchr(sub1, dot11_5g[i]) != NULL)
                is5g1 = 1;
            if(strchr(sub2, dot11_5g[i]) != NULL)
                is5g2 = 1;
        }
        if(is5g1 && is5g2)
            return(1);
    }
    /* Not compatible */
    return(0);
}

/********************** FREQUENCY SUBROUTINES ***********************/
/*
 * Note : the two functions below are the cause of troubles on
 * various embeeded platforms, as they are the reason we require
 * libm (math library).
 * In this case, please use enable BUILD_NOLIBM in the makefile
 *
 * FIXME : check negative mantissa and exponent
 */

/*------------------------------------------------------------------*/
/*
 * Convert a floating point the our internal representation of
 * frequencies.
 * The kernel doesn't want to hear about floating point, so we use
 * this custom format instead.
 */
    void
iw_float2freq(double	in,
        iwfreq *	out)
{
#ifdef WE_NOLIBM
    /* Version without libm : slower */
    out->e = 0;
    while(in > 1e9)
    {
        in /= 10;
        out->e++;
    }
    out->m = (long) in;
#else	/* WE_NOLIBM */
    /* Version with libm : faster */
    out->e = (short) (floor(log10(in)));
    if(out->e > 8)
    {
        out->m = ((long) (floor(in / pow(10,out->e - 6)))) * 100;
        out->e -= 8;
    }
    else
    {
        out->m = (long) in;
        out->e = 0;
    }
#endif	/* WE_NOLIBM */
}

/*------------------------------------------------------------------*/
/*
 * Convert our internal representation of frequencies to a floating point.
 */
    double
iw_freq2float(const iwfreq *	in)
{
#ifdef WE_NOLIBM
    /* Version without libm : slower */
    int		i;
    double	res = (double) in->m;
    for(i = 0; i < in->e; i++)
        res *= 10;
    return(res);
#else	/* WE_NOLIBM */
    /* Version with libm : faster */
    return ((double) in->m) * pow(10,in->e);
#endif	/* WE_NOLIBM */
}

/*------------------------------------------------------------------*/
/*
 * Output a frequency with proper scaling
 */
    void
iw_print_freq_value(char *	buffer,
        int		buflen,
        double	freq)
{
    if(freq < KILO)
        snprintf(buffer, buflen, "%g", freq);
    else
    {
        char	scale;
        int	divisor;

        if(freq >= GIGA)
        {
            scale = 'G';
            divisor = GIGA;
        }
        else
        {
            if(freq >= MEGA)
            {
                scale = 'M';
                divisor = MEGA;
            }
            else
            {
                scale = 'k';
                divisor = KILO;
            }
        }
        snprintf(buffer, buflen, "%g %cHz", freq / divisor, scale);
    }
}

/*------------------------------------------------------------------*/
/*
 * Output a frequency with proper scaling
 */
    void
iw_print_freq(char *	buffer,
        int	buflen,
        double	freq,
        int	channel,
        int	freq_flags)
{
    char	sep = ((freq_flags & IW_FREQ_FIXED) ? '=' : ':');
    char	vbuf[16];

    /* Print the frequency/channel value */
    iw_print_freq_value(vbuf, sizeof(vbuf), freq);

    /* Check if channel only */
    if(freq < KILO)
        snprintf(buffer, buflen, "Channel%c%s", sep, vbuf);
    else
    {
        /* Frequency. Check if we have a channel as well */
        if(channel >= 0)
            snprintf(buffer, buflen, "Frequency%c%s (Channel %d)",
                    sep, vbuf, channel);
        else
            snprintf(buffer, buflen, "Frequency%c%s", sep, vbuf);
    }
}

/*------------------------------------------------------------------*/
/*
 * Convert a frequency to a channel (negative -> error)
 */
    int
iw_freq_to_channel(double			freq,
        const struct iw_range *	range)
{
    double	ref_freq;
    int		k;

    /* Check if it's a frequency or not already a channel */
    if(freq < KILO)
        return(-1);

    /* We compare the frequencies as double to ignore differences
     * in encoding. Slower, but safer... */
    for(k = 0; k < range->num_frequency; k++)
    {
        ref_freq = iw_freq2float(&(range->freq[k]));
        if(freq == ref_freq)
            return(range->freq[k].i);
    }
    /* Not found */
    return(-2);
}

/*------------------------------------------------------------------*/
/*
 * Convert a channel to a frequency (negative -> error)
 * Return the channel on success
 */
    int
iw_channel_to_freq(int				channel,
        double *			pfreq,
        const struct iw_range *	range)
{
    int		has_freq = 0;
    int		k;

    /* Check if the driver support only channels or if it has frequencies */
    for(k = 0; k < range->num_frequency; k++)
    {
        if((range->freq[k].e != 0) || (range->freq[k].m > (int) KILO))
            has_freq = 1;
    }
    if(!has_freq)
        return(-1);

    /* Find the correct frequency in the list */
    for(k = 0; k < range->num_frequency; k++)
    {
        if(range->freq[k].i == channel)
        {
            *pfreq = iw_freq2float(&(range->freq[k]));
            return(channel);
        }
    }
    /* Not found */
    return(-2);
}

/*********************** BITRATE SUBROUTINES ***********************/

/*------------------------------------------------------------------*/
/*
 * Output a bitrate with proper scaling
 */
    void
iw_print_bitrate(char *	buffer,
        int	buflen,
        int	bitrate)
{
    double	rate = bitrate;
    char		scale;
    int		divisor;

    if(rate >= GIGA)
    {
        scale = 'G';
        divisor = GIGA;
    }
    else
    {
        if(rate >= MEGA)
        {
            scale = 'M';
            divisor = MEGA;
        }
        else
        {
            scale = 'k';
            divisor = KILO;
        }
    }
    snprintf(buffer, buflen, "%g %cb/s", rate / divisor, scale);
}

/************************ POWER SUBROUTINES *************************/

/*------------------------------------------------------------------*/
/*
 * Convert a value in dBm to a value in milliWatt.
 */
    int
iw_dbm2mwatt(int	in)
{
#ifdef WE_NOLIBM
    /* Version without libm : slower */
    int		ip = in / 10;
    int		fp = in % 10;
    int		k;
    double	res = 1.0;

    /* Split integral and floating part to avoid accumulating rounding errors */
    for(k = 0; k < ip; k++)
        res *= 10;
    for(k = 0; k < fp; k++)
        res *= LOG10_MAGIC;
    return((int) res);
#else	/* WE_NOLIBM */
    /* Version with libm : faster */
    return((int) (floor(pow(10.0, (((double) in) / 10.0)))));
#endif	/* WE_NOLIBM */
}

/*------------------------------------------------------------------*/
/*
 * Convert a value in milliWatt to a value in dBm.
 */
    int
iw_mwatt2dbm(int	in)
{
#ifdef WE_NOLIBM
    /* Version without libm : slower */
    double	fin = (double) in;
    int		res = 0;

    /* Split integral and floating part to avoid accumulating rounding errors */
    while(fin > 10.0)
    {
        res += 10;
        fin /= 10.0;
    }
    while(fin > 1.000001)	/* Eliminate rounding errors, take ceil */
    {
        res += 1;
        fin /= LOG10_MAGIC;
    }
    return(res);
#else	/* WE_NOLIBM */
    /* Version with libm : faster */
    return((int) (ceil(10.0 * log10((double) in))));
#endif	/* WE_NOLIBM */
}

/*------------------------------------------------------------------*/
/*
 * Output a txpower with proper conversion
 */
    void
iw_print_txpower(char *			buffer,
        int			buflen,
        struct iw_param *	txpower)
{
    int		dbm;

    /* Check if disabled */
    if(txpower->disabled)
    {
        snprintf(buffer, buflen, "off");
    }
    else
    {
        /* Check for relative values */
        if(txpower->flags & IW_TXPOW_RELATIVE)
        {
            snprintf(buffer, buflen, "%d", txpower->value);
        }
        else
        {
            /* Convert everything to dBm */
            if(txpower->flags & IW_TXPOW_MWATT)
                dbm = iw_mwatt2dbm(txpower->value);
            else
                dbm = txpower->value;

            /* Display */
            snprintf(buffer, buflen, "%d dBm", dbm);
        }
    }
}

/********************** STATISTICS SUBROUTINES **********************/

/*------------------------------------------------------------------*/
/*
 * Read /proc/net/wireless to get the latest statistics
 * Note : strtok not thread safe, not used in WE-12 and later.
 */
    int
iw_get_stats(int		skfd,
        const char *	ifname,
        iwstats *		stats,
        const iwrange *	range,
        int		has_range)
{
    /* Fortunately, we can always detect this condition properly */
    if((has_range) && (range->we_version_compiled > 11))
    {
        struct iwreq		wrq;
        wrq.u.data.pointer = (caddr_t) stats;
        wrq.u.data.length = sizeof(struct iw_statistics);
        wrq.u.data.flags = 1;		/* Clear updated flag */
        strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
        if(iw_get_ext(skfd, ifname, SIOCGIWSTATS, &wrq) < 0)
            return(-1);

        /* Format has not changed since WE-12, no conversion */
        return(0);
    }
    else
    {
        FILE *	f = fopen(PROC_NET_WIRELESS, "r");
        char	buf[256];
        char *	bp;
        int	t;

        if(f==NULL)
            return -1;
        /* Loop on all devices */
        while(fgets(buf,255,f))
        {
            bp=buf;
            while(*bp&&isspace(*bp))
                bp++;
            /* Is it the good device ? */
            if(strncmp(bp,ifname,strlen(ifname))==0 && bp[strlen(ifname)]==':')
            {
                /* Skip ethX: */
                bp=strchr(bp,':');
                bp++;
                /* -- status -- */
                bp = strtok(bp, " ");
                sscanf(bp, "%X", &t);
                stats->status = (unsigned short) t;
                /* -- link quality -- */
                bp = strtok(NULL, " ");
                if(strchr(bp,'.') != NULL)
                    stats->qual.updated |= 1;
                sscanf(bp, "%d", &t);
                stats->qual.qual = (unsigned char) t;
                /* -- signal level -- */
                bp = strtok(NULL, " ");
                if(strchr(bp,'.') != NULL)
                    stats->qual.updated |= 2;
                sscanf(bp, "%d", &t);
                stats->qual.level = (unsigned char) t;
                /* -- noise level -- */
                bp = strtok(NULL, " ");
                if(strchr(bp,'.') != NULL)
                    stats->qual.updated += 4;
                sscanf(bp, "%d", &t);
                stats->qual.noise = (unsigned char) t;
                /* -- discarded packets -- */
                bp = strtok(NULL, " ");
                sscanf(bp, "%d", &stats->discard.nwid);
                bp = strtok(NULL, " ");
                sscanf(bp, "%d", &stats->discard.code);
                bp = strtok(NULL, " ");
                sscanf(bp, "%d", &stats->discard.misc);
                fclose(f);
                /* No conversion needed */
                return 0;
            }
        }
        fclose(f);
        return -1;
    }
}

/*------------------------------------------------------------------*/
/*
 * Output the link statistics, taking care of formating
 */
    void
iw_print_stats(char *		buffer,
        int		buflen,
        const iwqual *	qual,
        const iwrange *	range,
        int		has_range)
{
    int		len;

    /* People are very often confused by the 8 bit arithmetic happening
     * here.
     * All the values here are encoded in a 8 bit integer. 8 bit integers
     * are either unsigned [0 ; 255], signed [-128 ; +127] or
     * negative [-255 ; 0].
     * Further, on 8 bits, 0x100 == 256 == 0.
     *
     * Relative/percent values are always encoded unsigned, between 0 and 255.
     * Absolute/dBm values are always encoded between -192 and 63.
     * (Note that up to version 28 of Wireless Tools, dBm used to be
     *  encoded always negative, between -256 and -1).
     *
     * How do we separate relative from absolute values ?
     * The old way is to use the range to do that. As of WE-19, we have
     * an explicit IW_QUAL_DBM flag in updated...
     * The range allow to specify the real min/max of the value. As the
     * range struct only specify one bound of the value, we assume that
     * the other bound is 0 (zero).
     * For relative values, range is [0 ; range->max].
     * For absolute values, range is [range->max ; 63].
     *
     * Let's take two example :
     * 1) value is 75%. qual->value = 75 ; range->max_qual.value = 100
     * 2) value is -54dBm. noise floor of the radio is -104dBm.
     *    qual->value = -54 = 202 ; range->max_qual.value = -104 = 152
     *
     * Jean II
     */

    /* Just do it...
     * The old way to detect dBm require both the range and a non-null
     * level (which confuse the test). The new way can deal with level of 0
     * because it does an explicit test on the flag. */
    if(has_range && ((qual->level != 0)
                || (qual->updated & (IW_QUAL_DBM | IW_QUAL_RCPI))))
    {
        /* Deal with quality : always a relative value */
        if(!(qual->updated & IW_QUAL_QUAL_INVALID))
        {
            len = snprintf(buffer, buflen, "Quality%c%d/%d  ",
                    qual->updated & IW_QUAL_QUAL_UPDATED ? '=' : ':',
                    qual->qual, range->max_qual.qual);
            buffer += len;
            buflen -= len;
        }

        /* Check if the statistics are in RCPI (IEEE 802.11k) */
        if(qual->updated & IW_QUAL_RCPI)
        {
            /* Deal with signal level in RCPI */
            /* RCPI = int{(Power in dBm +110)*2} for 0dbm > Power > -110dBm */
            if(!(qual->updated & IW_QUAL_LEVEL_INVALID))
            {
                double	rcpilevel = (qual->level / 2.0) - 110.0;
                len = snprintf(buffer, buflen, "Signal level%c%g dBm  ",
                        qual->updated & IW_QUAL_LEVEL_UPDATED ? '=' : ':',
                        rcpilevel);
                buffer += len;
                buflen -= len;
            }

            /* Deal with noise level in dBm (absolute power measurement) */
            if(!(qual->updated & IW_QUAL_NOISE_INVALID))
            {
                double	rcpinoise = (qual->noise / 2.0) - 110.0;
                len = snprintf(buffer, buflen, "Noise level%c%g dBm",
                        qual->updated & IW_QUAL_NOISE_UPDATED ? '=' : ':',
                        rcpinoise);
            }
        }
        else
        {
            /* Check if the statistics are in dBm */
            if((qual->updated & IW_QUAL_DBM)
                    || (qual->level > range->max_qual.level))
            {
                /* Deal with signal level in dBm  (absolute power measurement) */
                if(!(qual->updated & IW_QUAL_LEVEL_INVALID))
                {
                    int	dblevel = qual->level;
                    /* Implement a range for dBm [-192; 63] */
                    if(qual->level >= 64)
                        dblevel -= 0x100;
                    len = snprintf(buffer, buflen, "Signal level%c%d dBm  ",
                            qual->updated & IW_QUAL_LEVEL_UPDATED ? '=' : ':',
                            dblevel);
                    buffer += len;
                    buflen -= len;
                }

                /* Deal with noise level in dBm (absolute power measurement) */
                if(!(qual->updated & IW_QUAL_NOISE_INVALID))
                {
                    int	dbnoise = qual->noise;
                    /* Implement a range for dBm [-192; 63] */
                    if(qual->noise >= 64)
                        dbnoise -= 0x100;
                    len = snprintf(buffer, buflen, "Noise level%c%d dBm",
                            qual->updated & IW_QUAL_NOISE_UPDATED ? '=' : ':',
                            dbnoise);
                }
            }
            else
            {
                /* Deal with signal level as relative value (0 -> max) */
                if(!(qual->updated & IW_QUAL_LEVEL_INVALID))
                {
                    len = snprintf(buffer, buflen, "Signal level%c%d/%d  ",
                            qual->updated & IW_QUAL_LEVEL_UPDATED ? '=' : ':',
                            qual->level, range->max_qual.level);
                    buffer += len;
                    buflen -= len;
                }

                /* Deal with noise level as relative value (0 -> max) */
                if(!(qual->updated & IW_QUAL_NOISE_INVALID))
                {
                    len = snprintf(buffer, buflen, "Noise level%c%d/%d",
                            qual->updated & IW_QUAL_NOISE_UPDATED ? '=' : ':',
                            qual->noise, range->max_qual.noise);
                }
            }
        }
    }
    else
    {
        /* We can't read the range, so we don't know... */
        snprintf(buffer, buflen,
                "Quality:%d  Signal level:%d  Noise level:%d",
                qual->qual, qual->level, qual->noise);
    }
}

/*********************** ENCODING SUBROUTINES ***********************/

/*------------------------------------------------------------------*/
/*
 * Output the encoding key, with a nice formating
 */
    void
iw_print_key(char *			buffer,
        int			buflen,
        const unsigned char *	key,		/* Must be unsigned */
        int			key_size,
        int			key_flags)
{
    int	i;

    /* Check buffer size -> 1 bytes => 2 digits + 1/2 separator */
    if((key_size * 3) > buflen)
    {
        snprintf(buffer, buflen, "<too big>");
        return;
    }

    /* Is the key present ??? */
    if(key_flags & IW_ENCODE_NOKEY)
    {
        /* Nope : print on or dummy */
        if(key_size <= 0)
            strcpy(buffer, "on");			/* Size checked */
        else
        {
            strcpy(buffer, "**");			/* Size checked */
            buffer +=2;
            for(i = 1; i < key_size; i++)
            {
                if((i & 0x1) == 0)
                    strcpy(buffer++, "-");		/* Size checked */
                strcpy(buffer, "**");		/* Size checked */
                buffer +=2;
            }
        }
    }
    else
    {
        /* Yes : print the key */
        sprintf(buffer, "%.2X", key[0]);		/* Size checked */
        buffer +=2;
        for(i = 1; i < key_size; i++)
        {
            if((i & 0x1) == 0)
                strcpy(buffer++, "-");		/* Size checked */
            sprintf(buffer, "%.2X", key[i]);	/* Size checked */
            buffer +=2;
        }
    }
}

/*------------------------------------------------------------------*/
/*
 * Convert a passphrase into a key
 * ### NOT IMPLEMENTED ###
 * Return size of the key, or 0 (no key) or -1 (error)
 */
    static int
iw_pass_key(const char *	input,
        unsigned char *	key)
{
    input = input; key = key;
    fprintf(stderr, "Error: Passphrase not implemented\n");
    return(-1);
}

/*------------------------------------------------------------------*/
/*
 * Parse a key from the command line.
 * Return size of the key, or 0 (no key) or -1 (error)
 * If the key is too long, it's simply truncated...
 */
    int
iw_in_key(const char *		input,
        unsigned char *	key)
{
    int		keylen = 0;

    /* Check the type of key */
    if(!strncmp(input, "s:", 2))
    {
        /* First case : as an ASCII string (Lucent/Agere cards) */
        keylen = strlen(input + 2);		/* skip "s:" */
        if(keylen > IW_ENCODING_TOKEN_MAX)
            keylen = IW_ENCODING_TOKEN_MAX;
        memcpy(key, input + 2, keylen);
    }
    else
        if(!strncmp(input, "p:", 2))
        {
            /* Second case : as a passphrase (PrismII cards) */
            return(iw_pass_key(input + 2, key));		/* skip "p:" */
        }
        else
        {
            const char *	p;
            int		dlen;	/* Digits sequence length */
            unsigned char	out[IW_ENCODING_TOKEN_MAX];

            /* Third case : as hexadecimal digits */
            p = input;
            dlen = -1;

            /* Loop until we run out of chars in input or overflow the output */
            while(*p != '\0')
            {
                int	temph;
                int	templ;
                int	count;
                /* No more chars in this sequence */
                if(dlen <= 0)
                {
                    /* Skip separator */
                    if(dlen == 0)
                        p++;
                    /* Calculate num of char to next separator */
                    dlen = strcspn(p, "-:;.,");
                }
                /* Get each char separatly (and not by two) so that we don't
                 * get confused by 'enc' (=> '0E'+'0C') and similar */
                count = sscanf(p, "%1X%1X", &temph, &templ);
                if(count < 1)
                    return(-1);		/* Error -> non-hex char */
                /* Fixup odd strings such as '123' is '01'+'23' and not '12'+'03'*/
                if(dlen % 2)
                    count = 1;
                /* Put back two chars as one byte and output */
                if(count == 2)
                    templ |= temph << 4;
                else
                    templ = temph;
                out[keylen++] = (unsigned char) (templ & 0xFF);
                /* Check overflow in output */
                if(keylen >= IW_ENCODING_TOKEN_MAX)
                    break;
                /* Move on to next chars */
                p += count;
                dlen -= count;
            }
            /* We use a temporary output buffer 'out' so that if there is
             * an error, we don't overwrite the original key buffer.
             * Because of the way iwconfig loop on multiple key/enc arguments
             * until it finds an error in here, this is necessary to avoid
             * silently corrupting the encryption key... */
            memcpy(key, out, keylen);
        }

#ifdef DEBUG
    {
        char buf[IW_ENCODING_TOKEN_MAX * 3];
        iw_print_key(buf, sizeof(buf), key, keylen, 0);
        printf("Got key : %d [%s]\n", keylen, buf);
    }
#endif

    return(keylen);
}

/*------------------------------------------------------------------*/
/*
 * Parse a key from the command line.
 * Return size of the key, or 0 (no key) or -1 (error)
 */
    int
iw_in_key_full(int		skfd,
        const char *	ifname,
        const char *	input,
        unsigned char *	key,
        __u16 *		flags)
{
    int		keylen = 0;
    char *	p;

    if(!strncmp(input, "l:", 2))
    {
        struct iw_range	range;

        /* Extra case : as a login (user:passwd - Cisco LEAP) */
        keylen = strlen(input + 2) + 1;		/* skip "l:", add '\0' */
        /* Most user/password is 8 char, so 18 char total, < 32 */
        if(keylen > IW_ENCODING_TOKEN_MAX)
            keylen = IW_ENCODING_TOKEN_MAX;
        memcpy(key, input + 2, keylen);

        /* Separate the two strings */
        p = strchr((char *) key, ':');
        if(p == NULL)
        {
            fprintf(stderr, "Error: Invalid login format\n");
            return(-1);
        }
        *p = '\0';

        /* Extract range info */
        if(iw_get_range_info(skfd, ifname, &range) < 0)
            /* Hum... Maybe we should return an error ??? */
            memset(&range, 0, sizeof(range));

        if(range.we_version_compiled > 15)
        {

            printf("flags = %X, index = %X\n",
                    *flags, range.encoding_login_index);
            if((*flags & IW_ENCODE_INDEX) == 0)
            {
                /* Extract range info */
                if(iw_get_range_info(skfd, ifname, &range) < 0)
                    memset(&range, 0, sizeof(range));
                printf("flags = %X, index = %X\n", *flags, range.encoding_login_index);
                /* Set the index the driver expects */
                *flags |= range.encoding_login_index & IW_ENCODE_INDEX;
            }
            printf("flags = %X, index = %X\n", *flags, range.encoding_login_index);
        }
    }
    else
        /* Simpler routine above */
        keylen = iw_in_key(input, key);

    return(keylen);
}

/******************* POWER MANAGEMENT SUBROUTINES *******************/

/*------------------------------------------------------------------*/
/*
 * Output a power management value with all attributes...
 */
    void
iw_print_pm_value(char *	buffer,
        int		buflen,
        int		value,
        int		flags,
        int		we_version)
{
    /* Check size */
    if(buflen < 25)
    {
        snprintf(buffer, buflen, "<too big>");
        return;
    }
    buflen -= 25;

    /* Modifiers */
    if(flags & IW_POWER_MIN)
    {
        strcpy(buffer, " min");				/* Size checked */
        buffer += 4;
    }
    if(flags & IW_POWER_MAX)
    {
        strcpy(buffer, " max");				/* Size checked */
        buffer += 4;
    }

    /* Type */
    if(flags & IW_POWER_TIMEOUT)
    {
        strcpy(buffer, " timeout:");			/* Size checked */
        buffer += 9;
    }
    else
    {
        if(flags & IW_POWER_SAVING)
        {
            strcpy(buffer, " saving:");			/* Size checked */
            buffer += 8;
        }
        else
        {
            strcpy(buffer, " period:");			/* Size checked */
            buffer += 8;
        }
    }

    /* Display value without units */
    if(flags & IW_POWER_RELATIVE)
    {
        if(we_version < 21)
            value /= MEGA;
        snprintf(buffer, buflen, "%d", value);
    }
    else
    {
        /* Display value with units */
        if(value >= (int) MEGA)
            snprintf(buffer, buflen, "%gs", ((double) value) / MEGA);
        else
            if(value >= (int) KILO)
                snprintf(buffer, buflen, "%gms", ((double) value) / KILO);
            else
                snprintf(buffer, buflen, "%dus", value);
    }
}

/*------------------------------------------------------------------*/
/*
 * Output a power management mode
 */
    void
iw_print_pm_mode(char *	buffer,
        int	buflen,
        int	flags)
{
    /* Check size */
    if(buflen < 28)
    {
        snprintf(buffer, buflen, "<too big>");
        return;
    }

    /* Print the proper mode... */
    switch(flags & IW_POWER_MODE)
    {
        case IW_POWER_UNICAST_R:
            strcpy(buffer, "mode:Unicast only received");	/* Size checked */
            break;
        case IW_POWER_MULTICAST_R:
            strcpy(buffer, "mode:Multicast only received");	/* Size checked */
            break;
        case IW_POWER_ALL_R:
            strcpy(buffer, "mode:All packets received");	/* Size checked */
            break;
        case IW_POWER_FORCE_S:
            strcpy(buffer, "mode:Force sending");		/* Size checked */
            break;
        case IW_POWER_REPEATER:
            strcpy(buffer, "mode:Repeat multicasts");		/* Size checked */
            break;
        default:
            strcpy(buffer, "");				/* Size checked */
            break;
    }
}

/***************** RETRY LIMIT/LIFETIME SUBROUTINES *****************/

/*------------------------------------------------------------------*/
/*
 * Output a retry value with all attributes...
 */
    void
iw_print_retry_value(char *	buffer,
        int	buflen,
        int	value,
        int	flags,
        int	we_version)
{
    /* Check buffer size */
    if(buflen < 20)
    {
        snprintf(buffer, buflen, "<too big>");
        return;
    }
    buflen -= 20;

    /* Modifiers */
    if(flags & IW_RETRY_MIN)
    {
        strcpy(buffer, " min");				/* Size checked */
        buffer += 4;
    }
    if(flags & IW_RETRY_MAX)
    {
        strcpy(buffer, " max");				/* Size checked */
        buffer += 4;
    }
    if(flags & IW_RETRY_SHORT)
    {
        strcpy(buffer, " short");				/* Size checked */
        buffer += 6;
    }
    if(flags & IW_RETRY_LONG)
    {
        strcpy(buffer, "  long");				/* Size checked */
        buffer += 6;
    }

    /* Type lifetime of limit */
    if(flags & IW_RETRY_LIFETIME)
    {
        strcpy(buffer, " lifetime:");			/* Size checked */
        buffer += 10;

        /* Display value without units */
        if(flags & IW_RETRY_RELATIVE)
        {
            if(we_version < 21)
                value /= MEGA;
            snprintf(buffer, buflen, "%d", value);
        }
        else
        {
            /* Display value with units */
            if(value >= (int) MEGA)
                snprintf(buffer, buflen, "%gs", ((double) value) / MEGA);
            else
                if(value >= (int) KILO)
                    snprintf(buffer, buflen, "%gms", ((double) value) / KILO);
                else
                    snprintf(buffer, buflen, "%dus", value);
        }
    }
    else
        snprintf(buffer, buflen, " limit:%d", value);
}

/************************* TIME SUBROUTINES *************************/

/*------------------------------------------------------------------*/
/*
 * Print timestamps
 * Inspired from irdadump...
 */
    void
iw_print_timeval(char *				buffer,
        int				buflen,
        const struct timeval *		timev,
        const struct timezone *	tz)
{
    int s;

    s = (timev->tv_sec - tz->tz_minuteswest * 60) % 86400;
    snprintf(buffer, buflen, "%02d:%02d:%02d.%06u", 
            s / 3600, (s % 3600) / 60, 
            s % 60, (u_int32_t) timev->tv_usec);
}

/*********************** ADDRESS SUBROUTINES ************************/
/*
 * This section is mostly a cut & past from net-tools-1.2.0
 * (Well... This has evolved over the years)
 * manage address display and input...
 */

/*------------------------------------------------------------------*/
/*
 * Check if interface support the right MAC address type...
 */
    int
iw_check_mac_addr_type(int		skfd,
        const char *	ifname)
{
    struct ifreq		ifr;

    /* Get the type of hardware address */
    strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
    if((ioctl(skfd, SIOCGIFHWADDR, &ifr) < 0) ||
            ((ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER)
             && (ifr.ifr_hwaddr.sa_family != ARPHRD_IEEE80211)))
    {
        /* Deep trouble... */
        fprintf(stderr, "Interface %s doesn't support MAC addresses\n",
                ifname);
        return(-1);
    }

#ifdef DEBUG
    {
        char buf[20];
        printf("Hardware : %d - %s\n", ifr.ifr_hwaddr.sa_family,
                iw_saether_ntop(&ifr.ifr_hwaddr, buf));
    }
#endif

    return(0);
}


/*------------------------------------------------------------------*/
/*
 * Check if interface support the right interface address type...
 */
    int
iw_check_if_addr_type(int		skfd,
        const char *	ifname)
{
    struct ifreq		ifr;

    /* Get the type of interface address */
    strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
    if((ioctl(skfd, SIOCGIFADDR, &ifr) < 0) ||
            (ifr.ifr_addr.sa_family !=  AF_INET))
    {
        /* Deep trouble... */
        fprintf(stderr, "Interface %s doesn't support IP addresses\n", ifname);
        return(-1);
    }

#ifdef DEBUG
    printf("Interface : %d - 0x%lX\n", ifr.ifr_addr.sa_family,
            *((unsigned long *) ifr.ifr_addr.sa_data));
#endif

    return(0);
}

#if 0
/*------------------------------------------------------------------*/
/*
 * Check if interface support the right address types...
 */
    int
iw_check_addr_type(int		skfd,
        char *	ifname)
{
    /* Check the interface address type */
    if(iw_check_if_addr_type(skfd, ifname) < 0)
        return(-1);

    /* Check the interface address type */
    if(iw_check_mac_addr_type(skfd, ifname) < 0)
        return(-1);

    return(0);
}
#endif

#if 0
/*------------------------------------------------------------------*/
/*
 * Ask the kernel for the MAC address of an interface.
 */
    int
iw_get_mac_addr(int			skfd,
        const char *		ifname,
        struct ether_addr *	eth,
        unsigned short *	ptype)
{
    struct ifreq	ifr;
    int		ret;

    /* Prepare request */
    bzero(&ifr, sizeof(struct ifreq));
    strncpy(ifr.ifr_name, ifname, IFNAMSIZ);

    /* Do it */
    ret = ioctl(skfd, SIOCGIFHWADDR, &ifr);

    memcpy(eth->ether_addr_octet, ifr.ifr_hwaddr.sa_data, 6); 
    *ptype = ifr.ifr_hwaddr.sa_family;
    return(ret);
}
#endif

/*------------------------------------------------------------------*/
/*
 * Display an arbitrary length MAC address in readable format.
 */
    char *
iw_mac_ntop(const unsigned char *	mac,
        int				maclen,
        char *			buf,
        int				buflen)
{
    int	i;

    /* Overflow check (don't forget '\0') */
    if(buflen < (maclen * 3 - 1 + 1))
        return(NULL);

    /* First byte */
    sprintf(buf, "%02X", mac[0]);

    /* Other bytes */
    for(i = 1; i < maclen; i++)
        sprintf(buf + (i * 3) - 1, ":%02X", mac[i]);
    return(buf);
}

/*------------------------------------------------------------------*/
/*
 * Display an Ethernet address in readable format.
 */
    void
iw_ether_ntop(const struct ether_addr *	eth,
        char *			buf)
{
    sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X",
            eth->ether_addr_octet[0], eth->ether_addr_octet[1],
            eth->ether_addr_octet[2], eth->ether_addr_octet[3],
            eth->ether_addr_octet[4], eth->ether_addr_octet[5]);
}

/*------------------------------------------------------------------*/
/*
 * Display an Wireless Access Point Socket Address in readable format.
 * Note : 0x44 is an accident of history, that's what the Orinoco/PrismII
 * chipset report, and the driver doesn't filter it.
 */
    char *
iw_sawap_ntop(const struct sockaddr *	sap,
        char *			buf)
{
    const struct ether_addr ether_zero = {{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }};
    const struct ether_addr ether_bcast = {{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }};
    const struct ether_addr ether_hack = {{ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44 }};
    const struct ether_addr * ether_wap = (const struct ether_addr *) sap->sa_data;

    if(!iw_ether_cmp(ether_wap, &ether_zero))
        sprintf(buf, "Not-Associated");
    else
        if(!iw_ether_cmp(ether_wap, &ether_bcast))
            sprintf(buf, "Invalid");
        else
            if(!iw_ether_cmp(ether_wap, &ether_hack))
                sprintf(buf, "None");
            else
                iw_ether_ntop(ether_wap, buf);
    return(buf);
}

/*------------------------------------------------------------------*/
/*
 * Input an arbitrary length MAC address and convert to binary.
 * Return address size.
 */
    int
iw_mac_aton(const char *	orig,
        unsigned char *	mac,
        int			macmax)
{
    const char *	p = orig;
    int		maclen = 0;

    /* Loop on all bytes of the string */
    while(*p != '\0')
    {
        int	temph;
        int	templ;
        int	count;
        /* Extract one byte as two chars */
        count = sscanf(p, "%1X%1X", &temph, &templ);
        if(count != 2)
            break;			/* Error -> non-hex chars */
        /* Output two chars as one byte */
        templ |= temph << 4;
        mac[maclen++] = (unsigned char) (templ & 0xFF);

        /* Check end of string */
        p += 2;
        if(*p == '\0')
        {
#ifdef DEBUG
            char buf[20];
            iw_ether_ntop((const struct ether_addr *) mac, buf);
            fprintf(stderr, "iw_mac_aton(%s): %s\n", orig, buf);
#endif
            return(maclen);		/* Normal exit */
        }

        /* Check overflow */
        if(maclen >= macmax)
        {
#ifdef DEBUG
            fprintf(stderr, "iw_mac_aton(%s): trailing junk!\n", orig);
#endif
            errno = E2BIG;
            return(0);			/* Error -> overflow */
        }

        /* Check separator */
        if(*p != ':')
            break;
        p++;
    }

    /* Error... */
#ifdef DEBUG
    fprintf(stderr, "iw_mac_aton(%s): invalid ether address!\n", orig);
#endif
    errno = EINVAL;
    return(0);
}

/*------------------------------------------------------------------*/
/*
 * Input an Ethernet address and convert to binary.
 */
    int
iw_ether_aton(const char *orig, struct ether_addr *eth)
{
    int	maclen;
    maclen = iw_mac_aton(orig, (unsigned char *) eth, ETH_ALEN);
    if((maclen > 0) && (maclen < ETH_ALEN))
    {
        errno = EINVAL;
        maclen = 0;
    }
    return(maclen);
}

/*------------------------------------------------------------------*/
/*
 * Input an Internet address and convert to binary.
 */
    int
iw_in_inet(char *name, struct sockaddr *sap)
{
    struct hostent *hp;
    struct netent *np;
    struct sockaddr_in *sain = (struct sockaddr_in *) sap;

    /* Grmpf. -FvK */
    sain->sin_family = AF_INET;
    sain->sin_port = 0;

    /* Default is special, meaning 0.0.0.0. */
    if (!strcmp(name, "default")) {
        sain->sin_addr.s_addr = INADDR_ANY;
        return(1);
    }

    /* Try the NETWORKS database to see if this is a known network. */
    if ((np = getnetbyname(name)) != (struct netent *)NULL) {
        sain->sin_addr.s_addr = htonl(np->n_net);
        strcpy(name, np->n_name);
        return(1);
    }

    /* Always use the resolver (DNS name + IP addresses) */
    if ((hp = gethostbyname(name)) == (struct hostent *)NULL) {
        errno = h_errno;
        return(-1);
    }
    memcpy((char *) &sain->sin_addr, (char *) hp->h_addr_list[0], hp->h_length);
    strcpy(name, hp->h_name);
    return(0);
}

/*------------------------------------------------------------------*/
/*
 * Input an address and convert to binary.
 */
    int
iw_in_addr(int		skfd,
        const char *	ifname,
        char *	bufp,
        struct sockaddr *sap)
{
    /* Check if it is a hardware or IP address */
    if(strchr(bufp, ':') == NULL)
    {
        struct sockaddr	if_address;
        struct arpreq	arp_query;

        /* Check if we have valid interface address type */
        if(iw_check_if_addr_type(skfd, ifname) < 0)
        {
            fprintf(stderr, "%-8.16s  Interface doesn't support IP addresses\n", ifname);
            return(-1);
        }

        /* Read interface address */
        if(iw_in_inet(bufp, &if_address) < 0)
        {
            fprintf(stderr, "Invalid interface address %s\n", bufp);
            return(-1);
        }

        /* Translate IP addresses to MAC addresses */
        memcpy((char *) &(arp_query.arp_pa),
                (char *) &if_address,
                sizeof(struct sockaddr));
        arp_query.arp_ha.sa_family = 0;
        arp_query.arp_flags = 0;
        /* The following restrict the search to the interface only */
        /* For old kernels which complain, just comment it... */
        strncpy(arp_query.arp_dev, ifname, IFNAMSIZ);
        if((ioctl(skfd, SIOCGARP, &arp_query) < 0) ||
                !(arp_query.arp_flags & ATF_COM))
        {
            fprintf(stderr, "Arp failed for %s on %s... (%d)\nTry to ping the address before setting it.\n",
                    bufp, ifname, errno);
            return(-1);
        }

        /* Store new MAC address */
        memcpy((char *) sap,
                (char *) &(arp_query.arp_ha),
                sizeof(struct sockaddr));

#ifdef DEBUG
        {
            char buf[20];
            printf("IP Address %s => Hw Address = %s\n",
                    bufp, iw_saether_ntop(sap, buf));
        }
#endif
    }
    else	/* If it's an hardware address */
    {
        /* Check if we have valid mac address type */
        if(iw_check_mac_addr_type(skfd, ifname) < 0)
        {
            fprintf(stderr, "%-8.16s  Interface doesn't support MAC addresses\n", ifname);
            return(-1);
        }

        /* Get the hardware address */
        if(iw_saether_aton(bufp, sap) == 0)
        {
            fprintf(stderr, "Invalid hardware address %s\n", bufp);
            return(-1);
        }
    }

#ifdef DEBUG
    {
        char buf[20];
        printf("Hw Address = %s\n", iw_saether_ntop(sap, buf));
    }
#endif

    return(0);
}

/************************* MISC SUBROUTINES **************************/

/* Size (in bytes) of various events */
static const int priv_type_size[] = {
    0,				/* IW_PRIV_TYPE_NONE */
    1,				/* IW_PRIV_TYPE_BYTE */
    1,				/* IW_PRIV_TYPE_CHAR */
    0,				/* Not defined */
    sizeof(__u32),			/* IW_PRIV_TYPE_INT */
    sizeof(struct iw_freq),		/* IW_PRIV_TYPE_FLOAT */
    sizeof(struct sockaddr),	/* IW_PRIV_TYPE_ADDR */
    0,				/* Not defined */
};

/*------------------------------------------------------------------*/
/*
 * Max size in bytes of an private argument.
 */
    int
iw_get_priv_size(int	args)
{
    int	num = args & IW_PRIV_SIZE_MASK;
    int	type = (args & IW_PRIV_TYPE_MASK) >> 12;

    return(num * priv_type_size[type]);
}

/************************ EVENT SUBROUTINES ************************/
/*
 * The Wireless Extension API 14 and greater define Wireless Events,
 * that are used for various events and scanning.
 * Those functions help the decoding of events, so are needed only in
 * this case.
 */

/* -------------------------- CONSTANTS -------------------------- */

/* Type of headers we know about (basically union iwreq_data) */
#define IW_HEADER_TYPE_NULL	0	/* Not available */
#define IW_HEADER_TYPE_CHAR	2	/* char [IFNAMSIZ] */
#define IW_HEADER_TYPE_UINT	4	/* __u32 */
#define IW_HEADER_TYPE_FREQ	5	/* struct iw_freq */
#define IW_HEADER_TYPE_ADDR	6	/* struct sockaddr */
#define IW_HEADER_TYPE_POINT	8	/* struct iw_point */
#define IW_HEADER_TYPE_PARAM	9	/* struct iw_param */
#define IW_HEADER_TYPE_QUAL	10	/* struct iw_quality */

/* Handling flags */
/* Most are not implemented. I just use them as a reminder of some
 * cool features we might need one day ;-) */
#define IW_DESCR_FLAG_NONE	0x0000	/* Obvious */
/* Wrapper level flags */
#define IW_DESCR_FLAG_DUMP	0x0001	/* Not part of the dump command */
#define IW_DESCR_FLAG_EVENT	0x0002	/* Generate an event on SET */
#define IW_DESCR_FLAG_RESTRICT	0x0004	/* GET : request is ROOT only */
/* SET : Omit payload from generated iwevent */
#define IW_DESCR_FLAG_NOMAX	0x0008	/* GET : no limit on request size */
/* Driver level flags */
#define IW_DESCR_FLAG_WAIT	0x0100	/* Wait for driver event */

/* ---------------------------- TYPES ---------------------------- */

/*
 * Describe how a standard IOCTL looks like.
 */
struct iw_ioctl_description
{
    __u8	header_type;		/* NULL, iw_point or other */
    __u8	token_type;		/* Future */
    __u16	token_size;		/* Granularity of payload */
    __u16	min_tokens;		/* Min acceptable token number */
    __u16	max_tokens;		/* Max acceptable token number */
    __u32	flags;			/* Special handling of the request */
};

/* -------------------------- VARIABLES -------------------------- */

/*
 * Meta-data about all the standard Wireless Extension request we
 * know about.
 */
static const struct iw_ioctl_description standard_ioctl_descr[] = {
    [SIOCSIWCOMMIT	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_NULL,
    },
    [SIOCGIWNAME	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_CHAR,
        .flags		= IW_DESCR_FLAG_DUMP,
    },
    [SIOCSIWNWID	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
        .flags		= IW_DESCR_FLAG_EVENT,
    },
    [SIOCGIWNWID	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
        .flags		= IW_DESCR_FLAG_DUMP,
    },
    [SIOCSIWFREQ	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_FREQ,
        .flags		= IW_DESCR_FLAG_EVENT,
    },
    [SIOCGIWFREQ	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_FREQ,
        .flags		= IW_DESCR_FLAG_DUMP,
    },
    [SIOCSIWMODE	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_UINT,
        .flags		= IW_DESCR_FLAG_EVENT,
    },
    [SIOCGIWMODE	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_UINT,
        .flags		= IW_DESCR_FLAG_DUMP,
    },
    [SIOCSIWSENS	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCGIWSENS	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCSIWRANGE	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_NULL,
    },
    [SIOCGIWRANGE	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .max_tokens	= sizeof(struct iw_range),
        .flags		= IW_DESCR_FLAG_DUMP,
    },
    [SIOCSIWPRIV	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_NULL,
    },
    [SIOCGIWPRIV	- SIOCIWFIRST] = { /* (handled directly by us) */
        .header_type	= IW_HEADER_TYPE_NULL,
    },
    [SIOCSIWSTATS	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_NULL,
    },
    [SIOCGIWSTATS	- SIOCIWFIRST] = { /* (handled directly by us) */
        .header_type	= IW_HEADER_TYPE_NULL,
        .flags		= IW_DESCR_FLAG_DUMP,
    },
    [SIOCSIWSPY	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= sizeof(struct sockaddr),
        .max_tokens	= IW_MAX_SPY,
    },
    [SIOCGIWSPY	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= sizeof(struct sockaddr) +
            sizeof(struct iw_quality),
        .max_tokens	= IW_MAX_SPY,
    },
    [SIOCSIWTHRSPY	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= sizeof(struct iw_thrspy),
        .min_tokens	= 1,
        .max_tokens	= 1,
    },
    [SIOCGIWTHRSPY	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= sizeof(struct iw_thrspy),
        .min_tokens	= 1,
        .max_tokens	= 1,
    },
    [SIOCSIWAP	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_ADDR,
    },
    [SIOCGIWAP	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_ADDR,
        .flags		= IW_DESCR_FLAG_DUMP,
    },
    [SIOCSIWMLME	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .min_tokens	= sizeof(struct iw_mlme),
        .max_tokens	= sizeof(struct iw_mlme),
    },
    [SIOCGIWAPLIST	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= sizeof(struct sockaddr) +
            sizeof(struct iw_quality),
        .max_tokens	= IW_MAX_AP,
        .flags		= IW_DESCR_FLAG_NOMAX,
    },
    [SIOCSIWSCAN	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .min_tokens	= 0,
        .max_tokens	= sizeof(struct iw_scan_req),
    },
    [SIOCGIWSCAN	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .max_tokens	= IW_SCAN_MAX_DATA,
        .flags		= IW_DESCR_FLAG_NOMAX,
    },
    [SIOCSIWESSID	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .max_tokens	= IW_ESSID_MAX_SIZE + 1,
        .flags		= IW_DESCR_FLAG_EVENT,
    },
    [SIOCGIWESSID	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .max_tokens	= IW_ESSID_MAX_SIZE + 1,
        .flags		= IW_DESCR_FLAG_DUMP,
    },
    [SIOCSIWNICKN	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .max_tokens	= IW_ESSID_MAX_SIZE + 1,
    },
    [SIOCGIWNICKN	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .max_tokens	= IW_ESSID_MAX_SIZE + 1,
    },
    [SIOCSIWRATE	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCGIWRATE	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCSIWRTS	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCGIWRTS	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCSIWFRAG	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCGIWFRAG	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCSIWTXPOW	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCGIWTXPOW	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCSIWRETRY	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCGIWRETRY	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCSIWENCODE	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .max_tokens	= IW_ENCODING_TOKEN_MAX,
        .flags		= IW_DESCR_FLAG_EVENT | IW_DESCR_FLAG_RESTRICT,
    },
    [SIOCGIWENCODE	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .max_tokens	= IW_ENCODING_TOKEN_MAX,
        .flags		= IW_DESCR_FLAG_DUMP | IW_DESCR_FLAG_RESTRICT,
    },
    [SIOCSIWPOWER	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCGIWPOWER	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCSIWMODUL	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCGIWMODUL	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCSIWGENIE	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .max_tokens	= IW_GENERIC_IE_MAX,
    },
    [SIOCGIWGENIE	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .max_tokens	= IW_GENERIC_IE_MAX,
    },
    [SIOCSIWAUTH	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCGIWAUTH	- SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_PARAM,
    },
    [SIOCSIWENCODEEXT - SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .min_tokens	= sizeof(struct iw_encode_ext),
        .max_tokens	= sizeof(struct iw_encode_ext) +
            IW_ENCODING_TOKEN_MAX,
    },
    [SIOCGIWENCODEEXT - SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .min_tokens	= sizeof(struct iw_encode_ext),
        .max_tokens	= sizeof(struct iw_encode_ext) +
            IW_ENCODING_TOKEN_MAX,
    },
    [SIOCSIWPMKSA - SIOCIWFIRST] = {
        .header_type	= IW_HEADER_TYPE_POINT,
        .token_size	= 1,
        .min_tokens	= sizeof(struct iw_pmksa),
        .max_tokens	= sizeof(struct iw_pmksa),
    },
};
static const unsigned int standard_ioctl_num = (sizeof(standard_ioctl_descr) /
        sizeof(struct iw_ioctl_description));

    /*
     * Meta-data about all the additional standard Wireless Extension events
     * we know about.
     */
    static const struct iw_ioctl_description standard_event_descr[] = {
        [IWEVTXDROP	- IWEVFIRST] = {
            .header_type	= IW_HEADER_TYPE_ADDR,
        },
        [IWEVQUAL	- IWEVFIRST] = {
            .header_type	= IW_HEADER_TYPE_QUAL,
        },
        [IWEVCUSTOM	- IWEVFIRST] = {
            .header_type	= IW_HEADER_TYPE_POINT,
            .token_size	= 1,
            .max_tokens	= IW_CUSTOM_MAX,
        },
        [IWEVREGISTERED	- IWEVFIRST] = {
            .header_type	= IW_HEADER_TYPE_ADDR,
        },
        [IWEVEXPIRED	- IWEVFIRST] = {
            .header_type	= IW_HEADER_TYPE_ADDR, 
        },
        [IWEVGENIE	- IWEVFIRST] = {
            .header_type	= IW_HEADER_TYPE_POINT,
            .token_size	= 1,
            .max_tokens	= IW_GENERIC_IE_MAX,
        },
        [IWEVMICHAELMICFAILURE	- IWEVFIRST] = {
            .header_type	= IW_HEADER_TYPE_POINT, 
            .token_size	= 1,
            .max_tokens	= sizeof(struct iw_michaelmicfailure),
        },
        [IWEVASSOCREQIE	- IWEVFIRST] = {
            .header_type	= IW_HEADER_TYPE_POINT,
            .token_size	= 1,
            .max_tokens	= IW_GENERIC_IE_MAX,
        },
        [IWEVASSOCRESPIE	- IWEVFIRST] = {
            .header_type	= IW_HEADER_TYPE_POINT,
            .token_size	= 1,
            .max_tokens	= IW_GENERIC_IE_MAX,
        },
        [IWEVPMKIDCAND	- IWEVFIRST] = {
            .header_type	= IW_HEADER_TYPE_POINT,
            .token_size	= 1,
            .max_tokens	= sizeof(struct iw_pmkid_cand),
        },
    };
static const unsigned int standard_event_num = (sizeof(standard_event_descr) /
        sizeof(struct iw_ioctl_description));

    /* Size (in bytes) of various events */
    static const int event_type_size[] = {
        IW_EV_LCP_PK_LEN,	/* IW_HEADER_TYPE_NULL */
        0,
        IW_EV_CHAR_PK_LEN,	/* IW_HEADER_TYPE_CHAR */
        0,
        IW_EV_UINT_PK_LEN,	/* IW_HEADER_TYPE_UINT */
        IW_EV_FREQ_PK_LEN,	/* IW_HEADER_TYPE_FREQ */
        IW_EV_ADDR_PK_LEN,	/* IW_HEADER_TYPE_ADDR */
        0,
        IW_EV_POINT_PK_LEN,	/* Without variable payload */
        IW_EV_PARAM_PK_LEN,	/* IW_HEADER_TYPE_PARAM */
        IW_EV_QUAL_PK_LEN,	/* IW_HEADER_TYPE_QUAL */
    };

/*------------------------------------------------------------------*/
/*
 * Initialise the struct stream_descr so that we can extract
 * individual events from the event stream.
 */
    void
iw_init_event_stream(struct stream_descr *	stream,	/* Stream of events */
        char *			data,
        int			len)
{
    /* Cleanup */
    memset((char *) stream, '\0', sizeof(struct stream_descr));

    /* Set things up */
    stream->current = data;
    stream->end = data + len;
}

/*------------------------------------------------------------------*/
/*
 * Extract the next event from the event stream.
 */
    int
iw_extract_event_stream(struct stream_descr *	stream,	/* Stream of events */
        struct iw_event *	iwe,	/* Extracted event */
        int			we_version)
{
    const struct iw_ioctl_description *	descr = NULL;
    int		event_type = 0;
    unsigned int	event_len = 1;		/* Invalid */
    char *	pointer;
    /* Don't "optimise" the following variable, it will crash */
    unsigned	cmd_index;		/* *MUST* be unsigned */

    /* Check for end of stream */
    if((stream->current + IW_EV_LCP_PK_LEN) > stream->end)
        return(0);

#ifdef DEBUG
    printf("DBG - stream->current = %p, stream->value = %p, stream->end = %p\n",
            stream->current, stream->value, stream->end);
#endif

    /* Extract the event header (to get the event id).
     * Note : the event may be unaligned, therefore copy... */
    memcpy((char *) iwe, stream->current, IW_EV_LCP_PK_LEN);

#ifdef DEBUG
    printf("DBG - iwe->cmd = 0x%X, iwe->len = %d\n",
            iwe->cmd, iwe->len);
#endif

    /* Check invalid events */
    if(iwe->len <= IW_EV_LCP_PK_LEN)
        return(-1);

    /* Get the type and length of that event */
    if(iwe->cmd <= SIOCIWLAST)
    {
        cmd_index = iwe->cmd - SIOCIWFIRST;
        if(cmd_index < standard_ioctl_num)
            descr = &(standard_ioctl_descr[cmd_index]);
    }
    else
    {
        cmd_index = iwe->cmd - IWEVFIRST;
        if(cmd_index < standard_event_num)
            descr = &(standard_event_descr[cmd_index]);
    }
    if(descr != NULL)
        event_type = descr->header_type;
    /* Unknown events -> event_type=0 => IW_EV_LCP_PK_LEN */
    event_len = event_type_size[event_type];
    /* Fixup for earlier version of WE */
    if((we_version <= 18) && (event_type == IW_HEADER_TYPE_POINT))
        event_len += IW_EV_POINT_OFF;

    /* Check if we know about this event */
    if(event_len <= IW_EV_LCP_PK_LEN)
    {
        /* Skip to next event */
        stream->current += iwe->len;
        return(2);
    }
    event_len -= IW_EV_LCP_PK_LEN;

    /* Set pointer on data */
    if(stream->value != NULL)
        pointer = stream->value;			/* Next value in event */
    else
        pointer = stream->current + IW_EV_LCP_PK_LEN;	/* First value in event */

#ifdef DEBUG
    printf("DBG - event_type = %d, event_len = %d, pointer = %p\n",
            event_type, event_len, pointer);
#endif

    /* Copy the rest of the event (at least, fixed part) */
    if((pointer + event_len) > stream->end)
    {
        /* Go to next event */
        stream->current += iwe->len;
        return(-2);
    }
    /* Fixup for WE-19 and later : pointer no longer in the stream */
    /* Beware of alignement. Dest has local alignement, not packed */
    if((we_version > 18) && (event_type == IW_HEADER_TYPE_POINT))
        memcpy((char *) iwe + IW_EV_LCP_LEN + IW_EV_POINT_OFF,
                pointer, event_len);
    else
        memcpy((char *) iwe + IW_EV_LCP_LEN, pointer, event_len);

    /* Skip event in the stream */
    pointer += event_len;

    /* Special processing for iw_point events */
    if(event_type == IW_HEADER_TYPE_POINT)
    {
        /* Check the length of the payload */
        unsigned int	extra_len = iwe->len - (event_len + IW_EV_LCP_PK_LEN);
        if(extra_len > 0)
        {
            /* Set pointer on variable part (warning : non aligned) */
            iwe->u.data.pointer = pointer;

            /* Check that we have a descriptor for the command */
            if(descr == NULL)
                /* Can't check payload -> unsafe... */
                iwe->u.data.pointer = NULL;	/* Discard paylod */
            else
            {
                /* Those checks are actually pretty hard to trigger,
                 * because of the checks done in the kernel... */

                unsigned int	token_len = iwe->u.data.length * descr->token_size;

                /* Ugly fixup for alignement issues.
                 * If the kernel is 64 bits and userspace 32 bits,
                 * we have an extra 4+4 bytes.
                 * Fixing that in the kernel would break 64 bits userspace. */
                if((token_len != extra_len) && (extra_len >= 4))
                {
                    __u16		alt_dlen = *((__u16 *) pointer);
                    unsigned int	alt_token_len = alt_dlen * descr->token_size;
                    if((alt_token_len + 8) == extra_len)
                    {
#ifdef DEBUG
                        printf("DBG - alt_token_len = %d\n", alt_token_len);
#endif
                        /* Ok, let's redo everything */
                        pointer -= event_len;
                        pointer += 4;
                        /* Dest has local alignement, not packed */
                        memcpy((char *) iwe + IW_EV_LCP_LEN + IW_EV_POINT_OFF,
                                pointer, event_len);
                        pointer += event_len + 4;
                        iwe->u.data.pointer = pointer;
                        token_len = alt_token_len;
                    }
                }

                /* Discard bogus events which advertise more tokens than
                 * what they carry... */
                if(token_len > extra_len)
                    iwe->u.data.pointer = NULL;	/* Discard paylod */
                /* Check that the advertised token size is not going to
                 * produce buffer overflow to our caller... */
                if((iwe->u.data.length > descr->max_tokens)
                        && !(descr->flags & IW_DESCR_FLAG_NOMAX))
                    iwe->u.data.pointer = NULL;	/* Discard paylod */
                /* Same for underflows... */
                if(iwe->u.data.length < descr->min_tokens)
                    iwe->u.data.pointer = NULL;	/* Discard paylod */
#ifdef DEBUG
                printf("DBG - extra_len = %d, token_len = %d, token = %d, max = %d, min = %d\n",
                        extra_len, token_len, iwe->u.data.length, descr->max_tokens, descr->min_tokens);
#endif
            }
        }
        else
            /* No data */
            iwe->u.data.pointer = NULL;

        /* Go to next event */
        stream->current += iwe->len;
    }
    else
    {
        /* Ugly fixup for alignement issues.
         * If the kernel is 64 bits and userspace 32 bits,
         * we have an extra 4 bytes.
         * Fixing that in the kernel would break 64 bits userspace. */
        if((stream->value == NULL)
                && ((((iwe->len - IW_EV_LCP_PK_LEN) % event_len) == 4)
                    || ((iwe->len == 12) && ((event_type == IW_HEADER_TYPE_UINT) ||
                            (event_type == IW_HEADER_TYPE_QUAL))) ))
        {
#ifdef DEBUG
            printf("DBG - alt iwe->len = %d\n", iwe->len - 4);
#endif
            pointer -= event_len;
            pointer += 4;
            /* Beware of alignement. Dest has local alignement, not packed */
            memcpy((char *) iwe + IW_EV_LCP_LEN, pointer, event_len);
            pointer += event_len;
        }

        /* Is there more value in the event ? */
        if((pointer + event_len) <= (stream->current + iwe->len))
            /* Go to next value */
            stream->value = pointer;
        else
        {
            /* Go to next event */
            stream->value = NULL;
            stream->current += iwe->len;
        }
    }
    return(1);
}

/*********************** SCANNING SUBROUTINES ***********************/
/*
 * The Wireless Extension API 14 and greater define Wireless Scanning.
 * The normal API is complex, this is an easy API that return
 * a subset of the scanning results. This should be enough for most
 * applications that want to use Scanning.
 * If you want to have use the full/normal API, check iwlist.c...
 *
 * Precaution when using scanning :
 * The scanning operation disable normal network traffic, and therefore
 * you should not abuse of scan.
 * The scan need to check the presence of network on other frequencies.
 * While you are checking those other frequencies, you can *NOT* be on
 * your normal frequency to listen to normal traffic in the cell.
 * You need typically in the order of one second to actively probe all
 * 802.11b channels (do the maths). Some cards may do that in background,
 * to reply to scan commands faster, but they still have to do it.
 * Leaving the cell for such an extended period of time is pretty bad.
 * Any kind of streaming/low latency traffic will be impacted, and the
 * user will perceive it (easily checked with telnet). People trying to
 * send traffic to you will retry packets and waste bandwidth. Some
 * applications may be sensitive to those packet losses in weird ways,
 * and tracing those weird behavior back to scanning may take time.
 * If you are in ad-hoc mode, if two nodes scan approx at the same
 * time, they won't see each other, which may create associations issues.
 * For those reasons, the scanning activity should be limited to
 * what's really needed, and continuous scanning is a bad idea.
 * Jean II
 */

/*------------------------------------------------------------------*/
/*
 * Process/store one element from the scanning results in wireless_scan
 */
    static inline struct wireless_scan *
iw_process_scanning_token(struct iw_event *		event,
        struct wireless_scan *	wscan)
{
    struct wireless_scan *	oldwscan;

    /* Now, let's decode the event */
    switch(event->cmd)
    {
        case SIOCGIWAP:
            /* New cell description. Allocate new cell descriptor, zero it. */
            oldwscan = wscan;
            wscan = (struct wireless_scan *) malloc(sizeof(struct wireless_scan));
            if(wscan == NULL)
                return(wscan);
            /* Link at the end of the list */
            if(oldwscan != NULL)
                oldwscan->next = wscan;

            /* Reset it */
            bzero(wscan, sizeof(struct wireless_scan));

            /* Save cell identifier */
            wscan->has_ap_addr = 1;
            memcpy(&(wscan->ap_addr), &(event->u.ap_addr), sizeof (sockaddr));
            break;
        case SIOCGIWNWID:
            wscan->b.has_nwid = 1;
            memcpy(&(wscan->b.nwid), &(event->u.nwid), sizeof(iwparam));
            break;
        case SIOCGIWFREQ:
            wscan->b.has_freq = 1;
            wscan->b.freq = iw_freq2float(&(event->u.freq));
            wscan->b.freq_flags = event->u.freq.flags;
            break;
        case SIOCGIWMODE:
            wscan->b.mode = event->u.mode;
            if((wscan->b.mode < IW_NUM_OPER_MODE) && (wscan->b.mode >= 0))
                wscan->b.has_mode = 1;
            break;
        case SIOCGIWESSID:
            wscan->b.has_essid = 1;
            wscan->b.essid_on = event->u.data.flags;
            memset(wscan->b.essid, '\0', IW_ESSID_MAX_SIZE+1);
            if((event->u.essid.pointer) && (event->u.essid.length))
                memcpy(wscan->b.essid, event->u.essid.pointer, event->u.essid.length);
            break;
        case SIOCGIWENCODE:
            wscan->b.has_key = 1;
            wscan->b.key_size = event->u.data.length;
            wscan->b.key_flags = event->u.data.flags;
            if(event->u.data.pointer)
                memcpy(wscan->b.key, event->u.essid.pointer, event->u.data.length);
            else
                wscan->b.key_flags |= IW_ENCODE_NOKEY;
            break;
        case IWEVQUAL:
            /* We don't get complete stats, only qual */
            wscan->has_stats = 1;
            memcpy(&wscan->stats.qual, &event->u.qual, sizeof(struct iw_quality));
            break;
        case SIOCGIWRATE:
            /* Scan may return a list of bitrates. As we have space for only
             * a single bitrate, we only keep the largest one. */
            if((!wscan->has_maxbitrate) ||
                    (event->u.bitrate.value > wscan->maxbitrate.value))
            {
                wscan->has_maxbitrate = 1;
                memcpy(&(wscan->maxbitrate), &(event->u.bitrate), sizeof(iwparam));
            }
        case IWEVCUSTOM:
            /* How can we deal with those sanely ? Jean II */
        default:
            break;
    }	/* switch(event->cmd) */

    return(wscan);
}

/*------------------------------------------------------------------*/
/*
 * Initiate the scan procedure, and process results.
 * This is a non-blocking procedure and it will return each time
 * it would block, returning the amount of time the caller should wait
 * before calling again.
 * Return -1 for error, delay to wait for (in ms), or 0 for success.
 * Error code is in errno
 */
    int
iw_process_scan(int			skfd,
        char *			ifname,
        int			we_version,
        wireless_scan_head *	context)
{
    struct iwreq		wrq;
    unsigned char *	buffer = NULL;		/* Results */
    int			buflen = IW_SCAN_MAX_DATA; /* Min for compat WE<17 */
    unsigned char *	newbuf;

    /* Don't waste too much time on interfaces (150 * 100 = 15s) */
    context->retry++;
    if(context->retry > 150)
    {
        errno = ETIME;
        return(-1);
    }

    /* If we have not yet initiated scanning on the interface */
    if(context->retry == 1)
    {
        /* Initiate Scan */
        wrq.u.data.pointer = NULL;		/* Later */
        wrq.u.data.flags = 0;
        wrq.u.data.length = 0;
        /* Remember that as non-root, we will get an EPERM here */
        if((iw_set_ext(skfd, ifname, SIOCSIWSCAN, &wrq) < 0)
                && (errno != EPERM))
            return(-1);
        /* Success : now, just wait for event or results */
        return(250);	/* Wait 250 ms */
    }

realloc:
    /* (Re)allocate the buffer - realloc(NULL, len) == malloc(len) */
    newbuf = realloc(buffer, buflen);
    if(newbuf == NULL)
    {
        /* man says : If realloc() fails the original block is left untouched */
        if(buffer)
            free(buffer);
        errno = ENOMEM;
        return(-1);
    }
    buffer = newbuf;

    /* Try to read the results */
    wrq.u.data.pointer = buffer;
    wrq.u.data.flags = 0;
    wrq.u.data.length = buflen;
    if(iw_get_ext(skfd, ifname, SIOCGIWSCAN, &wrq) < 0)
    {
        /* Check if buffer was too small (WE-17 only) */
        if((errno == E2BIG) && (we_version > 16))
        {
            /* Some driver may return very large scan results, either
             * because there are many cells, or because they have many
             * large elements in cells (like IWEVCUSTOM). Most will
             * only need the regular sized buffer. We now use a dynamic
             * allocation of the buffer to satisfy everybody. Of course,
             * as we don't know in advance the size of the array, we try
             * various increasing sizes. Jean II */

            /* Check if the driver gave us any hints. */
            if(wrq.u.data.length > buflen)
                buflen = wrq.u.data.length;
            else
                buflen *= 2;

            /* Try again */
            goto realloc;
        }

        /* Check if results not available yet */
        if(errno == EAGAIN)
        {
            free(buffer);
            /* Wait for only 100ms from now on */
            return(100);	/* Wait 100 ms */
        }

        free(buffer);
        /* Bad error, please don't come back... */
        return(-1);
    }

    /* We have the results, process them */
    if(wrq.u.data.length)
    {
        struct iw_event		iwe;
        struct stream_descr	stream;
        struct wireless_scan *	wscan = NULL;
        int			ret;
#ifdef DEBUG
        /* Debugging code. In theory useless, because it's debugged ;-) */
        int	i;
        printf("Scan result [%02X", buffer[0]);
        for(i = 1; i < wrq.u.data.length; i++)
            printf(":%02X", buffer[i]);
        printf("]\n");
#endif

        /* Init */
        iw_init_event_stream(&stream, (char *) buffer, wrq.u.data.length);
        /* This is dangerous, we may leak user data... */
        context->result = NULL;

        /* Look every token */
        do
        {
            /* Extract an event and print it */
            ret = iw_extract_event_stream(&stream, &iwe, we_version);
            if(ret > 0)
            {
                /* Convert to wireless_scan struct */
                wscan = iw_process_scanning_token(&iwe, wscan);
                /* Check problems */
                if(wscan == NULL)
                {
                    free(buffer);
                    errno = ENOMEM;
                    return(-1);
                }
                /* Save head of list */
                if(context->result == NULL)
                    context->result = wscan;
            }
        }
        while(ret > 0);
    }

    /* Done with this interface - return success */
    free(buffer);
    return(0);
}

/*------------------------------------------------------------------*/
/*
 * Perform a wireless scan on the specified interface.
 * This is a blocking procedure and it will when the scan is completed
 * or when an error occur.
 *
 * The scan results are given in a linked list of wireless_scan objects.
 * The caller *must* free the result himself (by walking the list).
 * If there is an error, -1 is returned and the error code is available
 * in errno.
 *
 * The parameter we_version can be extracted from the range structure
 * (range.we_version_compiled - see iw_get_range_info()), or using
 * iw_get_kernel_we_version(). For performance reason, you should
 * cache this parameter when possible rather than querying it every time.
 *
 * Return -1 for error and 0 for success.
 */
    int
iw_scan(int			skfd,
        char *			ifname,
        int			we_version,
        wireless_scan_head *	context)
{
    int		delay;		/* in ms */

    /* Clean up context. Potential memory leak if(context.result != NULL) */
    context->result = NULL;
    context->retry = 0;

    /* Wait until we get results or error */
    while(1)
    {
        /* Try to get scan results */
        delay = iw_process_scan(skfd, ifname, we_version, context);

        /* Check termination */
        if(delay <= 0)
            break;

        /* Wait a bit */
        usleep(delay * 1000);
    }

    /* End - return -1 or 0 */
    return(delay);
}
